﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MacomberMapSystem.Common.Internals;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.Database;
using System.ComponentModel;
using MacomberMapSystem.Common.Processing;
using MacomberMapSystem.Common.CIM.Direct;
using System.Reflection;
using System.Drawing;
using System.Text.RegularExpressions;
using System.IO;

namespace MacomberMapSystem.Common.User_Interfaces
{
    /// <summary>
    /// This class holds the UI helpers
    /// </summary>
    public class MM_UserInterface_Helpers
    {
        #region Variable declarations
        /// <summary>Our application repository</summary>
        public MM_Repository Repository;

        /// <summary>Our label for memory updates</summary>
        public ToolStripStatusLabel lblMem;

        /// <summary>Our label for CPU usage updates</summary>
        public ToolStripStatusLabel lblCPU;

        /// <summary>Our label for thread count</summary>
        public ToolStripStatusLabel lblThreads;


        /// <summary>Our main application toolstrip</summary>
        public StatusStrip ssMain;

        /// <summary>Our collection of model labels</summary>
        private Dictionary<MM_Database_Model, ModelLabel> ModelLabels = new Dictionary<MM_Database_Model, ModelLabel>();

        /// <summary>Our collection of process labels</summary>
        private Dictionary<CIM_Processor, ProcessLabel> ProcessLabels = new Dictionary<CIM_Processor, ProcessLabel>();

        /// <summary>Our collection of worker bars</summary>
        private Dictionary<BackgroundWorker, ToolStripProgressBar> WorkerBars = new Dictionary<BackgroundWorker, ToolStripProgressBar>();

        /// <summary>Our menu strip</summary>
        private ContextMenuStrip cms = new ContextMenuStrip();
        #endregion


        #region Initialization
        /// <summary>
        /// Initialize a new UI helper
        /// </summary>
        /// <param name="ssMain"></param>
        /// <param name="Repository"></param>
        /// <param name="ShowWorkers"></param>
        /// <param name="lblCPU"></param>
        /// <param name="lblMem"></param>
        /// <param name="lblThreads"></param>
        public MM_UserInterface_Helpers(StatusStrip ssMain, ToolStripStatusLabel lblMem, ToolStripStatusLabel lblCPU, ToolStripStatusLabel lblThreads, MM_Repository Repository, bool ShowWorkers)
        {
            this.ssMain = ssMain;
            this.lblMem = lblMem;
            this.lblCPU = lblCPU;
            this.lblThreads = lblThreads;
            this.Repository = Repository;
            cms.ItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            if (ShowWorkers)
                Repository.WorkerAdded += new MM_Repository.BackgroundWorkerDelegate(Repository_WorkerAdded);

        }

        #endregion

        /// <summary>
        /// Update the status of the CPU and memory usage of this application
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void tmrUpdate_Tick(object sender, EventArgs e)
        {         
            try
            {
                long Mem;
                lblCPU.Text = "CPU: " + MM_System_Interface.GetCPUUsage(out Mem) + "%";
                lblThreads.Text = "Threads: " + System.Diagnostics.Process.GetCurrentProcess().Threads.Count.ToString("#,##0");
                if (Mem > 1024 * 1024 * 1024)
                    lblMem.Text = "Mem: " + (Mem / (1024 * 1024)).ToString("#,##0 M");
                else if (Mem > 1024 * 1024)
                    lblMem.Text = "Mem: " + (Mem / (1024)).ToString("#,##0 K");
                else
                    lblMem.Text = "Mem: " + Mem.ToString("#,##0");
                

                //Update our model labels
                ModelLabel lbl;
                foreach (MM_Database_Model Model in Repository.Model_Collection.Values)
                    if (Model.State == MM_Database_Model.enumModelState.Unopened && ModelLabels.TryGetValue(Model, out lbl))
                    {
                        ssMain.Items.Remove(lbl);
                        ModelLabels.Remove(Model);
                    }
                    else if (Model.State != MM_Database_Model.enumModelState.Unopened)
                    {
                        if (!ModelLabels.TryGetValue(Model, out lbl))
                            ModelLabels.Add(Model, new ModelLabel(Model, lbl_MouseUp, ssMain));
                        else
                            lbl.Invalidate();
                    }


                ProcessLabel pLbl;
                foreach (CIM_Processor Process in Repository.ActiveProcesses)
                    if (Process.State == CIM_Processor.enumProcessState.Completed)
                    {
                        if (ProcessLabels.TryGetValue(Process, out pLbl))
                        {
                            ssMain.Items.Remove(pLbl);
                            ProcessLabels.Remove(Process);
                        }
                    }
                    else if (!ProcessLabels.TryGetValue(Process, out pLbl))
                        ProcessLabels.Add(Process, new ProcessLabel(Process, ssMain));
                    else
                        pLbl.Invalidate();
            }
            catch
            { }
        }

        /// <summary>
        /// Handle a right-click option
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lbl_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                MM_Database_Model mdl = (sender as ModelLabel).Model;
                cms.Items.Clear();
                foreach (FieldInfo fI in mdl.GetType().GetFields())
                    try
                    {
                        if (fI.GetValue(mdl) == null)
                            cms.Items.Add(fI.Name + ": (null)").Enabled = false;
                        else if (fI.FieldType == typeof(String))
                            cms.Items.Add(fI.Name + ": " + fI.GetValue(mdl).ToString()).Enabled = false;
                        else if (fI.FieldType == typeof(int))
                            cms.Items.Add(fI.Name + ": " + Convert.ToInt32(fI.GetValue(mdl)).ToString("#,##0")).Enabled = false;
                        else if (fI.FieldType == typeof(float))
                            cms.Items.Add(fI.Name + ": " + Convert.ToSingle(fI.GetValue(mdl)).ToString("#,##0.00")).Enabled = false;
                        else if (fI.FieldType == typeof(UInt64))
                            cms.Items.Add(fI.Name + ": " + Convert.ToUInt64(fI.GetValue(mdl)).ToString("#,##0")).Enabled = false;
                        else if (fI.FieldType.IsEnum)
                            cms.Items.Add(fI.Name + ": " + fI.GetValue(mdl).ToString()).Enabled = false;
                    }
                    catch { }
                foreach (PropertyInfo pI in mdl.GetType().GetProperties())
                    try
                    {
                        if (pI.GetValue(mdl, null) == null)
                            cms.Items.Add(pI.Name + ": (null)").Enabled = false;
                        else if (pI.PropertyType == typeof(String))
                            cms.Items.Add(pI.Name + ": " + pI.GetValue(mdl, null).ToString()).Enabled = false;
                        else if (pI.PropertyType == typeof(int))
                            cms.Items.Add(pI.Name + ": " + Convert.ToInt32(pI.GetValue(mdl, null)).ToString("#,##0")).Enabled = false;
                        else if (pI.PropertyType == typeof(float))
                            cms.Items.Add(pI.Name + ": " + Convert.ToSingle(pI.GetValue(mdl, null)).ToString("#,##0.00")).Enabled = false;
                        else if (pI.PropertyType == typeof(UInt64))
                            cms.Items.Add(pI.Name + ": " + Convert.ToUInt64(pI.GetValue(mdl, null)).ToString("#,##0")).Enabled = false;
                        else if (pI.PropertyType.IsEnum)
                            cms.Items.Add(pI.Name + ": " + pI.GetValue(mdl, null).ToString()).Enabled = false;
                    }
                    catch { }

                cms.Items.Add("-");
                cms.Items.Add("Show &CIM browser");
                cms.Items.Add("&Unload model");
                if (mdl.CachedElements == null)
                    cms.Items.Add("&Track element access");
                else
                    cms.Items.Add("&Save " + mdl.CachedElements.Count.ToString("#,##0") + " accessed elements as model");
                cms.Tag = mdl;
                cms.Show((sender as ToolStripLabel).Owner, e.Location);
            }
        }

        /// <summary>
        /// Handle the click of an option
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cms_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            MM_Database_Model mdl = (sender as ContextMenuStrip).Tag as MM_Database_Model;
            if (e.ClickedItem.Text == "&Track element access")
                mdl.CachedElements = new Dictionary<CIM.CIM_RdfID, CIM.CIM_Element>();
            else if (e.ClickedItem.Text == "Show &CIM browser")
                new MacomberMapSystem.Common.User_Interfaces.CIM_Browser.frmCIMBrowser(mdl).Show();
            else if (e.ClickedItem.Text.EndsWith("accessed elements as model"))
                using (SaveFileDialog sFd = new SaveFileDialog())
                {
                    sFd.Title = "Save CIM model";
                    sFd.Filter = "CIM model (*.xml)|*.xml";
                    sFd.FileName = "ModelExtract-" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + ".xml";
                    if (sFd.ShowDialog() != DialogResult.OK)
                        return;
                    using (StreamWriter sW = new StreamWriter(sFd.FileName))
                    {
                        sW.WriteLine("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:cim=\"http://iec.ch/TC57/2006/CIM-schema-cim10#\" xmlns:spc=\"http://www.siemens-ptd/SHIMM1.0#\" xmlns:etx=\"http://www.ercot.com/CIM11R0/2008/2.0/extension#\">");
                        foreach (Common.CIM.CIM_Element Elem2 in mdl.CachedElements.Values)
                            sW.WriteLine("\t" + Elem2.XML);
                        sW.WriteLine("</rdf:RDF>");
                    }
                    mdl.CachedElements.Clear();
                    mdl.CachedElements = null;
                }
            else if (e.ClickedItem.Text == "&Unload model" && MessageBox.Show("Are you sure you would like to unload model " + mdl.Name + "?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                lock (mdl)
                {
                    mdl.State = MM_Database_Model.enumModelState.Unopened;
                    Clear(mdl.Attributes);
                    Clear(mdl.AttributesByType);
#if !NoCacheing
                    Clear(mdl.ElementAttributes);
                    Clear(mdl.ElementQueue);
#endif
                    Clear(mdl.ElemTypes);
                    Clear(mdl.ModelChanges);
                    Clear(mdl.ReservedTraceNames);
                    if (mdl.sConn != null)
                        mdl.sConn.Close();
                    Clear(mdl.Substations);
                    Clear(mdl.TraceLinkages);
                    Clear(mdl.Traces);
                    Clear(mdl.Types);
                    Clear(mdl.TypesByAttribute);
                    Clear(mdl.Zones);

                    if (mdl is CIM_DirectModel)
                    {
                        CIM_DirectModel mdl2 = mdl as CIM_DirectModel;
                        Clear(mdl2._Attributes);
                        Clear(mdl2._AttributesByType);
                        Clear(mdl2._TypesByAttribute);
                        Clear(mdl2.ElementsByType);
                        Clear(mdl2.ReverseTEIDs);
                        Clear(mdl2.TEIDs);
#if !NoCacheing
                        Clear(mdl2.TempElements);
#endif
                        Clear(mdl2.TypeFromTypes);
                        Clear(mdl2.TypeToTypes);
                    }
                    GC.Collect();
                }
        }

        /// <summary>
        /// Clear a list/dictionary
        /// </summary>
        /// <param name="ListToClear"></param>
        private void Clear(Object ListToClear)
        {
            //Clear, and if needed, dispose our list.
            if (ListToClear == null)
                return;
            ListToClear.GetType().GetMethod("Clear").Invoke(ListToClear, new object[0]);
            if (ListToClear is IDisposable)
                (ListToClear as IDisposable).Dispose();
            }

        private delegate void SafeSetModelColorText(ToolStripStatusLabel lbl, Color ModelColor, MM_Database_Model.enumModelState ModelState);
        /// <summary>
        /// Set our model's label, color and text
        /// </summary>
        /// <param name="lbl"></param>
        /// <param name="ModelColor"></param>
        /// <param name="ModelState"></param>
        private void SetModelColorText(ToolStripStatusLabel lbl, Color ModelColor, MM_Database_Model.enumModelState ModelState)
        {
            if (lbl.Owner.InvokeRequired)
                lbl.Owner.Invoke(new SafeSetModelColorText(SetModelColorText), lbl, ModelColor, ModelState);
            else
            {
                lbl.ToolTipText = ModelState.ToString().Replace('_', ' ');
                lbl.BackColor = ModelColor;
            }
        }

        /// <summary>
        /// Update our progress bar
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            BackgroundWorker bw = sender as BackgroundWorker;
            ToolStripProgressBar pb = WorkerBars[bw];
            if (pb.Value == e.ProgressPercentage)
            { }
            else if (ssMain.InvokeRequired)
                ssMain.Invoke(new ProgressChangedEventHandler(Worker_ProgressChanged), sender, e);
            else
                pb.Value = e.ProgressPercentage;
        }

        /// <summary>
        /// Remove a run worker when completed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (ssMain.InvokeRequired)
                ssMain.Invoke(new RunWorkerCompletedEventHandler(Worker_RunWorkerCompleted), sender, e);
            else
            {
                BackgroundWorker bw = sender as BackgroundWorker;
                ToolStripProgressBar pb = WorkerBars[bw];
                WorkerBars.Remove(bw);
                ssMain.Items.Remove(pb);
                pb.Dispose();
            }
        }

        /// <summary>
        /// Add a background worker to our queue
        /// </summary>
        /// <param name="Worker"></param>
        /// <param name="Name"></param>
        private void Repository_WorkerAdded(BackgroundWorker Worker, string Name)
        {
            if (ssMain.InvokeRequired)
                ssMain.Invoke(new MM_Repository.BackgroundWorkerDelegate(Repository_WorkerAdded), Worker, Name);
            else
            {
                ToolStripProgressBar pb = new ToolStripProgressBar(Name);
                pb.ToolTipText = Name;
                WorkerBars.Add(Worker, pb);
                pb.Value = 0;
                pb.Maximum = 100;
                ssMain.Items.Add(pb);
                Worker.ProgressChanged += new ProgressChangedEventHandler(Worker_ProgressChanged);
                Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Worker_RunWorkerCompleted);
            }
        }

        /// <summary>
        /// Attempt to interpret a model's information based on filename
        /// </summary>
        /// <param name="FileName"></param>
        /// <param name="cmbModelClass"></param>
        /// <param name="dtValidStart"></param>
        /// <param name="txtModelIteration"></param>
        /// <param name="cmbModelCategory"></param>
        /// <param name="Models"></param>
        /// <param name="cmbLastModel"></param>
        public static void InterpretModel(String FileName, ComboBox cmbModelClass, DateTimePicker dtValidStart, TextBox txtModelIteration, ComboBox cmbModelCategory, Dictionary<int, MM_Database_Model> Models, ComboBox cmbLastModel)
        {
            //Try and guess at the class
            Match RegMatch = Regex.Match(Path.GetFileNameWithoutExtension(FileName), @"CIM_\d\d");
            if (RegMatch.Success)
                cmbModelClass.Text = RegMatch.Value;
            else
                cmbModelClass.Text = "";

            //Try and guess at the model valid start date                    
            RegMatch = Regex.Match(Path.GetFileNameWithoutExtension(FileName), @"\d\d\d\d\d\d\d\d");
            if (RegMatch.Success)
                try
                {
                    dtValidStart.Value = new DateTime(int.Parse(RegMatch.Value.Substring(4)), int.Parse(RegMatch.Value.Substring(0, 2)), int.Parse(RegMatch.Value.Substring(2, 2)));
                    txtModelIteration.Text = Path.GetFileNameWithoutExtension(FileName).Substring(0, Path.GetFileNameWithoutExtension(FileName).IndexOf(RegMatch.Value) - 1).TrimEnd('_');


                    //Now, try and guess at our prior model based on the valid start date and model class
                    MM_Database_Model LastModel = null;
                    foreach (MM_Database_Model Model in Models.Values)
                        if (LastModel == null || (Model.ValidStart > LastModel.ValidStart && Model.ValidStart < dtValidStart.Value))
                            LastModel = Model;

                    //Also, test out our name and turn [Mon]_ML to number.
                    int YearOffset = dtValidStart.Value.Year - 2012;
                    for (int Month = 1; Month <= 12; Month++)
                        if (txtModelIteration.Text.Contains(new DateTime(dtValidStart.Value.Year, Month, 1).ToString("MMM") + "_ML"))
                        {
                            txtModelIteration.Text = txtModelIteration.Text.Replace(new DateTime(dtValidStart.Value.Year, Month, 1).ToString("MMM") + "_ML", ((12 * YearOffset) + 15 + Month).ToString() + "_");
                            cmbModelClass.Text = "CIM_" + ((12 * YearOffset) + 15 + Month).ToString();
                        }
                    cmbLastModel.SelectedValue = LastModel;
                    if (FileName.Contains("_OE"))
                        cmbModelCategory.Text = "OE Future";
                    else
                        cmbModelCategory.Text = "Current";

                }
                catch (Exception) { }


        }
    }
}
